package com.handspring;

import com.sun.xml.internal.ws.util.StringUtils;

import java.beans.Introspector;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.*;

/**
 * @author: ztl
 * @date: 2024/04/17 22:29
 * @desc: 代表着spring容器中的ApplicationContext
 */
public class ZhengApplicationContext {

    /**
     * 就类似于启动类中的那个配置类App.class 一样的那个class
     */
    private Class configClass;
    /**
     * 用来存bean的定义的一个map,key是beanName，v是beanDefinition
     * 一旦根据beanName在这个map中搜不到，说明整个spring管理的全部bean中，都不存在你这个bean
     */
    private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>();

    /**
     * 单例池，用来存单例对象的，也就是spring容器中的对象
     */
    private Map<String,Object> singletonObject = new HashMap<String, Object>();

    /**
     * 用来存放beanPostProcessor对象的（因为我们是扫描的时候，发现他是实现的beanPostProcessor，
     * 但是我们初始化对象完了以后，才会执行beanPostProcessor的前置、后置方法。
     * 毕竟没初始化，你想执行都没法执行啊）
     * spring底层实际是LinkedList
     */
    private List<MyBeanPostProcessor> beanPostProcessorList= new ArrayList<>();


    /**
     * 新增一个有参构造，待会传参用
     * @param configClass
     */
    public ZhengApplicationContext(Class configClass) {
        this.configClass = configClass;

        // 扫描
        scan(configClass);


        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();

            if ("singleton".equals(beanDefinition.getScope())){
                // 创建bean，然后放到一个地方
                Object bean = createBean(beanName, beanDefinition);
                // 创建完的bean，放到单例池里面
                singletonObject.put(beanName,bean);
            }
        }
    }

    /**
     * 提供一个创建bean的方法，他的参数是beanName和beanDefinition
     * @param beanName
     * @param beanDefinition
     * @return  抽象出来的话，不管是单例、多例，谁创建对象都可以调用这个方法
     */
    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        Class clazz = beanDefinition.getType();
        Object instance = null;
        try {
            instance = clazz.getConstructor().newInstance();

            // 依赖注入
            // 创建完这个bean以后，拿到这个bean的全部属性，用来依赖注入，注意是，getDeclaredFields，不是getFields
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(MyAutowired.class)) {

                    // 我们可以从单例池里面拿，但是要考虑到，假设我们创建userService的时候，orderService还没创建呢，那单例池肯定就没了
                    // 即使单例池里面没有，那我们的beanDefinition的map也一定会有。因为我们是全部bean都扫描完了才去进行下一步，才创建bean的

//                    Class<?> type = field.getType(); // 正常情况下，spring中我们是先根据type去找，然后在根据name去找，这里我们省个事，(假设名字一样)直接用名字找
                    String name = field.getName(); // 这里是类似于： orderService
                    Object filterBean = getBean(name);

                    field.setAccessible(true);
                    field.set(instance,filterBean);

                }
            }

            // 依赖注入完了,初始化之前，实现beanNameAware功能
            if (instance instanceof MyBeanNameAware){
                ((MyBeanNameAware) instance).setBeanName(beanName);
            }

            // 执行beanPostProcessor的前置操作
            // 注意注意注意 : beanPostProcessor 这是spring中几乎是最重要的东西了。
            for (MyBeanPostProcessor beanPostProcessor : beanPostProcessorList){
                // 执行前置操作、后置操作,你传参是instance，返回也是instance，就可以做一些强化处理，这就是aop的底层
                // 注意，这个方法一旦调用，返回的就是这个对象的代理对象了。因为我们写逻辑了，返回这个对象的代理对象
                // 简单的说，我们传进来以后userService的BeanDefinition对象，返回的是userService的代理对象
                instance = beanPostProcessor.postProcessBeforeInitialization(instance,beanName);
            }

            // 对象创建完以后，执行初始化相关操作
            if (instance instanceof MyInitializingBean){
                ((MyInitializingBean)instance).afterPropertiesSet();
            }


            for (MyBeanPostProcessor beanPostProcessor : beanPostProcessorList){
                // 执行beanPostProcessor的后置操作
                instance = beanPostProcessor.postProcessAfterInitialization(instance,beanName);
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return instance;
    }


    /**
     * 模拟spring中的扫描过程.  主要流程：
     *      1、解析传进来的类，得到扫描文件
     *      2、拿到扫描文件的路径，去拿到这些class对象
     *      4、拿到这个路径下的全部文件，去加载，得到一个class对象
     *      5、遍历这些文件，判断哪些是有@Component
     *      6、解析这些文件，有没有scope注解啊，bean的名字啊，，，得到一个beanDefinition对象
     *      7、遍历的时候，给每个bean都生成一个beanDefinition对象，放到一个map中去
     *
     * @param configClass
     */
    private void scan(Class configClass) {
        if (configClass.isAnnotationPresent(MyComponentScan.class)){
            MyComponentScan annotation = (MyComponentScan) configClass.getAnnotation(MyComponentScan.class);
            // 此时的路径是这样的：    com.testspring.service   我们希望的是： d://a/b....这样的
            // java中有类加载器：
            //      根加载器(bootStarpClassLoader)     他只加载jar/lib下的文件
            //      扩展类(ext)                        他只加载jre/ext/lib下的文件
            //      应用程序类(app)          他只加载，你自己项目下的路径，就是咱们平时说的target文件夹下，程序员自己写的那些类。我们此时就是用的这个特性
            //      自定义类加载器
            String path = annotation.value();
            // com/testspring/service
            String replacePath = path.replace(".", "/");
            // 换成相对路径
            ClassLoader classLoader = configClass.getClassLoader();
            // file:/D:/java/ruanjian/idea/file/spring_frame_2/target/classes/com/testspring/service
            URL resource = classLoader.getResource(replacePath);

            // 拿到路径后，我们要找到target中对应的class文件
            File file = new File(resource.getFile());
            if (file.isDirectory()){
                for (File f : file.listFiles()) {
                    // D:\java\ruanjian\idea\file\spring_frame_2\target\classes\com\testspring\service\OrderService.class
                    // D:\java\ruanjian\idea\file\spring_frame_2\target\classes\com\testspring\service\UserService.class
                    String filePath = f.getAbsolutePath();

                    // 从 D:\java\ruanjian\idea\file\spring_frame_2\target\classes\com\testspring\service\OrderService.class
                    // 变成了： com\testspring\service\UserService.class
                    filePath = filePath.substring(filePath.indexOf("com"),filePath.indexOf(".class"));
                    filePath = filePath.replace("\\",".");

                    // 获取class对象
                    Class<?> clazz = null;
                    try {
                        clazz = classLoader.loadClass(filePath);

                        // 判断这个class带不带@Component注解，需不需要被spring管理
                        if (clazz.isAnnotationPresent(MyComponent.class)){
                            // 判断这个bean是不是BeanPostProcessor的实现类、如果是的话，放到一个list中去
                            if (MyBeanPostProcessor.class.isAssignableFrom(clazz)){
                                // 将实现了beanPostProcessor的类，放到一个list中去
                                MyBeanPostProcessor instance = (MyBeanPostProcessor)clazz.getConstructor().newInstance();
                                beanPostProcessorList.add(instance);
                            }


                            // 拿到bean的name,就类似于， @Component("userService")一样，来拿到这个userService
                            MyComponent myComponent = clazz.getAnnotation(MyComponent.class);
                            String beanName = myComponent.value();

                            if (beanName == null || "".equals(beanName)){
                                // 如果你直接是@Component，而不是@Component("userService"),那么就赋值一下。
                                beanName = Introspector.decapitalize(clazz.getSimpleName());
                            }


                            // 判断是不是单例bean，单例bean才需要被spring放到容器中，多例bean每次重新new就行
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setType(clazz);

                            if (clazz.isAnnotationPresent(MyScope.class)){
                                // 你的类上有@Scope，就需要判断是@Scope("prototype")还是@Scope("singleton")
                                MyScope myScope = clazz.getAnnotation(MyScope.class);
                                String value = myScope.value();
                                beanDefinition.setScope(value);
                            }else {
                                // 你的类上都没@Scope，那默认就是单例的
                                beanDefinition.setScope("singleton");
                            }

                            //这个类扫描完以后，放到beanMap的对象中去
                            beanDefinitionMap.put(beanName,beanDefinition);
                        }




                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }

                }
            }

        }
    }

    /**
     * 代表着从spring容器中根据beanName返回对象。
     * @param beanName
     * @return
     */
    public Object getBean(String beanName){
        if (!beanDefinitionMap.keySet().contains(beanName)){
            throw new RuntimeException("兄弟，你的beanName是瞎写的吧，整个spring容器内压根没你这个bean");
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if ("singleton".equals(beanDefinition.getScope())){
            Object singleton = singletonObject.get(beanName);
            if (singleton == null){
                singleton = createBean(beanName,beanDefinition);
                singletonObject.put(beanName,singleton);
            }
            return singleton;
        }else {
            // 多例(原型),每次get的时候都是不同的新的对象，所以直接调生成对象的方法就行
            Object prototypeBean = createBean(beanName, beanDefinition);
            return prototypeBean;
        }
    }

}
